﻿using System;
using System.Collections.Generic;
using Ensemble;
using CommonUtils;
using System.Threading;

namespace SearchBackEnd
{
    using BackEndMessage = CommonUtils.ServerMessage<MessageType>;
    class BEGroupCommunication
    {
        static Connection conn = null;
        static Member memb = null;
        static String[] currView = null;
        static int recoverAckCount = 1;
        static int primaryBalanceAckCount = 1;
        static int secondaryBalanceAckCount = 1;
        static bool done = false;
        

        public BEGroupCommunication(Connection conn, Member memb)
        {
            BEGroupCommunication.conn = conn;
            BEGroupCommunication.memb = memb;
        }

        /// <summary>
        /// Stops this back end server
        /// </summary>
        public void quit()
        {
            if (done) return;
            done = true;
            memb.Leave();   
        }

        /// <summary>
        /// Main loop for message handeling
        /// </summary>
        public void mainLoop()
        {
            while (!done)
            {
                // Read all waiting messages from Ensmeble
                while (conn.Poll() && !done)
                {
                    Message msg = conn.Recv();
                    switch (msg.mtype)
                    {
                        case UpType.VIEW:
                            recoverAckCount = 1;
                            primaryBalanceAckCount = 1;
                            secondaryBalanceAckCount = 1;
                            //first time in any view
                            if (currView == null)
                            {
                                MessageHandler.SetGroupID(memb.current_view.endpt);
                                currView = memb.current_view.view;
                            }
                            else {
                                if (memb.current_view.view.Length == 1) {
                                    Print.ColorEmit("I'm the last back end - quiting... press any key to terminate.", Print.Foreground.Blue);
                                    quit();
                                    continue;
                                }
                            }

                            Print.PrintNewViewMsg(memb, msg);

                            foreach (string endpt in msg.view.view)
                            {
                                Print.DebugEmit("   " + endpt);
                            }
                            Print.DebugEmit("]");

                            List<String> currV = new List<string>(memb.current_view.view);
                            List<String> oldV = new List<string>(currView);
                            List<String> newServers = new List<string>(memb.current_view.view);
                            List<String> removedServers = new List<string>(currView);
                            currView = memb.current_view.view;// update view

                            //creating new servers list
                            foreach (String s in oldV)
                                newServers.Remove(s);
                            //creating removed servers list
                            foreach (String s in currV)
                                removedServers.Remove(s);
                            //adding servers to count verctor.
                            MessageHandler.ServersAdded(newServers.ToArray());
                            //recovering lost pages
                            MessageHandler.Recover(removedServers.ToArray());
                            break;
                        case UpType.CAST:
                        case UpType.SEND:
                            BackEndMessage incomingMsg = BackEndMessage.Deserialize(msg.data);
                            switch (incomingMsg.MessageType)
                            {
                                case MessageType.AddPage:
                                    pageToIndex page = (pageToIndex)incomingMsg.Params;
                                    if (page.dest.Equals(memb.current_view.endpt))
                                    {
                                        MessageHandler.AddPage(page.page, page.isPrimary, page.otherAddr);
                                    }
                                    else 
                                    {
                                        MessageHandler.IncCount(page.dest, page.isPrimary);
                                    }
                                    break;
                                case MessageType.VectorUpdate:
                                    CounterVector.VectorValues vectorValues = (CounterVector.VectorValues)incomingMsg.Params;
                                    String senderId = memb.current_view.view[msg.origin];
                                    MessageHandler.UpdateCount(senderId, vectorValues);
                                    break;
                                case MessageType.UpdateOther:
                                    pageToIndex page1 = (pageToIndex)incomingMsg.Params;
                                    MessageHandler.UpdatePageOther(page1.page, page1.otherAddr);
                                    break;
                                case MessageType.DoneRecoverPhase:
                                    recoverAckCount++;
                                    if (recoverAckCount == memb.current_view.view.Length)
                                    {
                                        MessageHandler.BalanceIndex(true);
                                    }
                                    break;
                                case MessageType.DonePrimaryBalancePhase:
                                    primaryBalanceAckCount++;
                                    if (primaryBalanceAckCount == memb.current_view.view.Length)
                                    {
                                        MessageHandler.UpdateMyCount();
                                        MessageHandler.BalanceIndex(false);

                                    }
                                    break;
                                case MessageType.DoneSecondaryBalancePhase:
                                    secondaryBalanceAckCount++;
                                    if (secondaryBalanceAckCount == memb.current_view.view.Length)
                                    {
                                        MessageHandler.UpdateMyCount();
                                    }
                                    break;


                            }
                            break;
                        case UpType.BLOCK:
                            memb.BlockOk();
                            break;
                        case UpType.EXIT:
                            break;
                    }
                    Console.Out.Flush();
                }
                Thread.Sleep(100);
            }
        }

        /// <summary>
        /// sends a message to everyone with my updated primary & seconday page count
        /// </summary>
        /// <param name="vectorValues"></param>
        public static void updateCount(CounterVector.VectorValues vectorValues)
        {

            BackEndMessage msg = new BackEndMessage(MessageType.VectorUpdate, vectorValues);
            sendMsg(null, msg.Serialize());
        }

        /// <summary>
        /// Send an index request to another server - this also informs other servers to update their counts for this server.
        /// </summary>
        /// <param name="dest">the ep address of the server.</param>
        /// <param name="p">the page to add.</param>
        /// <param name="isPrimary">is this a primary or secondary page.</param>
        /// <param name="otherAddr">the other server that has this page.</param>
        public static void sendIndexRequest(string dest, Page p, Boolean isPrimary, String otherAddr)
        {
            BackEndMessage msg = new BackEndMessage(MessageType.AddPage, new pageToIndex(p, isPrimary, otherAddr,dest));
            MessageHandler.IncCount(dest, isPrimary);
            sendMsg(null, msg.Serialize());
        }

        /// <summary>
        /// send a message that indicates that this server finished the recovery phase
        /// </summary>
        public static void endRecovery()
        {
            BackEndMessage msg = new BackEndMessage(MessageType.DoneRecoverPhase, null);
            sendMsg(null, msg.Serialize());
        }
        /// <summary>
        /// send a message that indicates that this server finished the primary balance phase
        /// </summary>
        public static void endPrimaryBalance()
        {
            BackEndMessage msg = new BackEndMessage(MessageType.DonePrimaryBalancePhase, null);
            sendMsg(null, msg.Serialize());
        }
        /// <summary>
        /// send a message that indicates that this server finished the secondary balance phase
        /// </summary>
        public static void endSecondaryBalance()
        {
            BackEndMessage msg = new BackEndMessage(MessageType.DoneSecondaryBalancePhase, null);
            sendMsg(null, msg.Serialize());
        }

        /// <summary>
        /// Update the "other" pointer of a server to a new server
        /// </summary>
        /// <param name="currOther">the server that is holding the other copy of "p"</param>
        /// <param name="p">the page to change</param>
        /// <param name="newOtherAddr">the new "other" address</param>
        public static void updateOther(String currOther, Page p, String newOtherAddr)
        {
            BackEndMessage msg = new BackEndMessage(MessageType.UpdateOther, new pageToIndex(p, false, newOtherAddr, null));
            sendMsg(currOther, msg.Serialize());
        }

        /// <summary>
        /// sends a group comunication message
        /// </summary>
        /// <param name="dest">an EP address to send this message to or null if this is a Cast message</param>
        /// <param name="msg">the message to send</param>
        private static void sendMsg(string dest, byte[] msg)
        {
            while (true)
            {
                lock (conn)
                {
                    if (memb.current_status == Member.Status.Normal)
                    {
                        if (dest != null)
                            memb.Send1(getDestInt(dest), msg);
                        else
                            memb.Cast(msg);
                        break;
                    }
                }
                //can't transmit - wait & try again
                Thread.Sleep(50);
            }
        }

        /// <summary>
        /// Converts EP address to index in the view array - used for sending messages
        /// </summary>
        /// <param name="epAddr">the end point address</param>
        /// <returns>the correlating index in the view array</returns>
        private static int getDestInt(string epAddr)
        {
            for (int i = 0; i < memb.current_view.view.Length; i++)
            {
                if (memb.current_view.view[i].Equals(epAddr)) return i;
            }
            return -1;
        }

        [Serializable]
        class pageToIndex
        {
            public pageToIndex(Page page, Boolean isPrimary, String otherAddr, String dest)
            {
                this.dest = dest;
                this.page = page;
                this.isPrimary = isPrimary;
                this.otherAddr = otherAddr;
            }

            public Page page { get; set; }
            public Boolean isPrimary { get; set; }
            public String otherAddr { get; set; }
            public String dest { get; set; }
        }

        
    }
}
